//
// GPSMapEdit
// (c) Konstantin Galichsky (kg@geopainting.com), 2002-2007
// Garmin and MapSource are registered trademarks or trademarks of Garmin Ltd. or one of its subsidiaries.
//
// Garmin MapSource MPS waypoints/tracks export.
//

# include "StdAfx.h"
# include "MpsFile.h"
# include "MapSourceHelpers.h"
# include "Save.h"
# include "Map.h"
# include "Status.h"
# include "SaveFileDlg.h"
# include "FileWriter.h"
# include "fast_floor.h"
# include "Globals.h"

class CMpsExport : public CMapSaver {
  public:
	virtual bool CanSave (const map_t & _map) {return true;}
	virtual bool Save (HANDLE _hFile, const char * _strFileName, const map_t & _map);

  private:
	bool _Save (HANDLE _hFile, const map_t & _map);
};

DECLARE_WPTRK_EXPORT (CMpsExport, "garmin-mps", "mps", "Garmin MapSource file (*.mps)");

bool CMpsExport::Save (HANDLE _hFile, const char * _strFileName, const map_t & _map) {
	if (_hFile != NULL) {
		return _Save (_hFile, _map);
	} else {
		//
		// Prepare default file name.
		//
		char strFileName [MAX_PATH + 1];
		::strncpy (strFileName, _strFileName, MAX_PATH);
		strFileName [MAX_PATH] = '\0';
		if (! SaveFileDlg ("mps", "MapSource file (*.mps)\0*.mps\0", strFileName))
			return false;

		//
		// Open the file.
		//
		const CFileForWrite hFile (strFileName);
		if (! hFile.IsValid ()) {
			ErrorMsgBox ("Can't create file:\n %s", strFileName);
			return false;
		}

		return _Save (hFile, _map);
	}
}

bool CMpsExport::_Save (HANDLE _hFile, const map_t & _map) {
	CFileWriter fw (_hFile);

	names_t setWptNames, setTrkNames;

	//
	// Write the file.
	//
	SetProgressStatus (0);

	// Save header.
	fw.WriteBin (g_pMpsHeader, c_cMpsHeaderSize);

	size_t cRgn = 0;
	const size_t cRgns = _map.GetRgnCount ();
	for (size_t cKind = 0; cKind < rkMax; ++ cKind) {
		const layer_t & layer = _map.layers [cKind];
		const rgns_t & rgns = layer.rgns;
		for (rgns_t::const_iterator iRgn = rgns.begin (); iRgn != rgns.end (); ++ iRgn, ++ cRgn) {
			const rgn_t & rgn = * iRgn;
			if (rgn.IsDeleted () || ! rgn.IsWTR ())
				continue;

			// Save waypoint.
			for (size_t cOrigin = 0; cOrigin < rgn.origins.size (); ++ cOrigin) {
				const rgn_origin_t & origin = rgn.origins [cOrigin];
				if (origin.bDeleted)
					continue;

				const waypoint_info_t * const pWptExtra = rgn.pExtra->wtr.wpt ();

				// Generate unique name.
				string_t strName;
				GenerateUniqueNameForMapSource (rgn.strLabel, setWptNames, strName);

				CMpsRecordHdr hdr;
				hdr.dwLen = strName.size () + 1 + sizeof (CMpsWaypoint2) + rgn.strComment.size () + sizeof (CMpsWaypoint3);
				hdr.btType = 'W';
				fw.WriteBin (hdr);

				const size_t cPos = fw.GetPos ();

				// Name.
				fw.WriteS (strName);
				fw.WriteC ('\0');

				CMpsWaypoint2 wp2;
				::memset (& wp2, 0, sizeof (wp2));
				wp2.dwMapObjectID = 0xFFFFFFFF;
				wp2.dwSubclass10  = 0xFFFFFFFF;
				wp2.dwSubclass11  = 0xFFFFFFFF;
				wp2.dwSubclass3   = 0xFFFFFFFF;

				// Position.
				wp2.lX = fast_floor (origin.point.x*(1UL << 31)/180.f + .5f);
				wp2.lY = fast_floor (origin.point.y*(1UL << 31)/180.f + .5f);

				// Altitude.
				if (pWptExtra && (pWptExtra->wFlags & waypoint_info_t::defAltitude)) {
					wp2.btHasAltitude = 1;
					wp2.dblAltitude = pWptExtra->fAltitude;
				}

				fw.WriteBin (& wp2, sizeof (wp2) - sizeof (wp2.strDescription));

				// Comment.
				fw.WriteS (rgn.strComment);
				fw.WriteC ('\0');

				CMpsWaypoint3 wp3;
				::memset (& wp3, 0, sizeof (wp3));

				// Show.
				if (pWptExtra) {
					switch (pWptExtra->GetShow ()) {
						default:
						case waypoint_info_t::showHide:        wp3.dwShow = 1; break;
						case waypoint_info_t::showSymbol:      wp3.dwShow = 0; break;
						case waypoint_info_t::showName:        wp3.dwShow = 1; break;
						case waypoint_info_t::showDescription: wp3.dwShow = 2; break;
					}
				} else
					wp3.dwShow = 1;

				// Color.
				wp3.dwColor = 0;

				// Type.
				if (pWptExtra && (pWptExtra->wFlags & waypoint_info_t::defGarminType))
					wp3.dwType = pWptExtra->wGarminType;
				else
					wp3.dwType = 0x12; // Plain waypoint.

				// Depth.
				if (pWptExtra && (pWptExtra->wFlags & waypoint_info_t::defDepth)) {
					wp3.btHasDepth = 1;
					wp3.dblDepth = pWptExtra->fDepth;
				}

				// Proximity.
				if (pWptExtra && (pWptExtra->wFlags & waypoint_info_t::defProximity)) {
					wp3.btHasProximity = 1;
					wp3.dblProximity = pWptExtra->fProximity;
				}

				fw.WriteBin (wp3);

				assert (fw.GetPos () - cPos == hdr.dwLen);
			}

			// TODO: save route.
			if (rgn.IsRoute ())
				continue;

			// Save track.
			for (size_t cElement = 0; cElement < rgn.elements.size (); ++ cElement) {
				const rgn_element_t & element = rgn.elements [cElement];
				if (element.bDeleted)
					continue;
				const points_t & points = element.points;
				const size_t cPoints = points.size ();
				if (cPoints == 0)
					continue;

				const track_info_t * const pTrkExtra = rgn.pExtra ? rgn.pExtra->wtr.track () : NULL;
				const rgn_element_extra_t * const pTrkExtra2 = element.pExtra ? & * element.pExtra : NULL;
				const size_t cPointsExtra = pTrkExtra ? pTrkExtra2->TrackPoints.size () : 0;

				// Generate unique name.
				string_t strName;
				GenerateUniqueNameForMapSource (rgn.strLabel, setTrkNames, strName);

				// Header.
				CMpsRecordHdr hdr;
				hdr.dwLen  = strName.size () + 1 + sizeof (CMpsTrack2) + sizeof (CMpsTrackPoint)*(cPoints - 1);
				hdr.btType = 'T';
				fw.WriteBin (hdr);

				const size_t cPos = fw.GetPos ();

				// Name.
				fw.WriteS (strName);
				fw.WriteC ('\0');

				CMpsTrack2 tr2;
				::memset (& tr2, 0, sizeof (tr2));
				tr2.btDisplayOnGPS = pTrkExtra && (pTrkExtra->wFlags & track_info_t::defHideInGPS) ? 0 : 1;
				tr2.dwColor        = pTrkExtra && (pTrkExtra->wFlags & track_info_t::defLineColor) ? FindNearestGarminTrackColor (pTrkExtra->colLine) : 0;
				tr2.dwPoints       = cPoints;
				fw.WriteBin (& tr2, sizeof (tr2) - sizeof (tr2.points));

				for (size_t cPoint = 0; cPoint < cPoints; ++ cPoint) {
					CMpsTrackPoint pt;
					::memset (& pt, 0, sizeof (pt));

					// Position.
					pt.lX = fast_floor (points [cPoint].x*(1UL << 31)/180.f + .5f);
					pt.lY = fast_floor (points [cPoint].y*(1UL << 31)/180.f + .5f);

					if (pTrkExtra && cPoint < cPointsExtra) {
						const trackpoint_info_t & PointExtra = pTrkExtra2->TrackPoints [cPoint];

						// Altitude.
						if (PointExtra.wFlags & trackpoint_info_t::defAltitude) {
							pt.btHasAltitude = 1;
							pt.dblAltitude   = PointExtra.fAltitude;
						}

						// Depth.
						if (PointExtra.wFlags & trackpoint_info_t::defDepth) {
							pt.btHasDepth = 1;
							pt.dblDepth   = PointExtra.fDepth;
						}

						// Time.
						if (PointExtra.wFlags & trackpoint_info_t::defTime) {
							pt.btHasTime  = 1;
							pt.dwDateTime = fast_floor_d ((PointExtra.dtTime - 25569.)*24.*60.*60. + .5);
						}
					}

					fw.WriteBin (pt);
				}

				assert (fw.GetPos () - cPos == hdr.dwLen);
			}

			SetProgressStatus (fast_floor (100.f*cRgn/cRgns + .5f));
		}
	}

	// Save tail record.
	fw.WriteBin (g_pMpsTail, c_cMpsTailSize);

	fw.FlushBuffer ();

	SetProgressStatus (100);

	if (fw.GetOverflow ())
		ErrorMsgBox ("File has been written partialy.\nThis may be caused by disk overflow.");

	return true;
}
